package com.cyy


import com.jfinal.kit.Kv
import com.jfinal.kit.StrKit
import com.jfinal.template.Engine
import javafx.event.EventTarget
import javafx.scene.image.Image
import javafx.scene.paint.Color
import javafx.scene.text.FontWeight
import javafx.stage.Stage
import org.fxmisc.richtext.CodeArea
import org.fxmisc.richtext.LineNumberFactory
import tornadofx.*
import java.util.regex.Pattern
import org.fxmisc.richtext.model.StyleSpansBuilder
import org.fxmisc.richtext.model.StyleSpans
import java.time.Duration
import java.util.*


class EnjoyApp : App(EnjoyView::class, Mystyle::class) {
    override fun start(stage: Stage) {
        super.start(stage)
        addStageIcon(Image("Java_logo.png"))

//        2种加载样式的方法：1、通过加载样式文件， 2、通过定义样式类
//        stage.scene.stylesheets.add(resources.url("/java-keywords.css").toExternalForm())
    }
}

fun EventTarget.codearea(op: (CodeArea.() -> Unit) = {}) = opcr(this, CodeArea(), op)

class EnjoyView : View("Jfinal enjoy 模板引擎练习小工具") {
    val engine = Engine.use().setEncoding("utf-8").addSharedObject("dt", Date()).addSharedMethod(StrKit())
    var kv = Kv.create()
    val strOut = stringProperty()
    override val root = borderpane {
        top = label("提示：在左边文本框中点击鼠标右键，选择相应的菜单进行操作，右边的文本框会显示结果") {
            paddingAll = 10.0
            style{
                textFill= Color.RED
            }
        }
        center = hbox(5) {
            codearea {
                val ca = this
                contextmenu {
                    item("渲染所有").action {
                        strOut.value = renderStr(ca.text, kv)
                    }
                    item("渲染当前行").action {
                        strOut.value = renderStr(ca.getText(ca.currentParagraph), kv)
                    }
                    item("渲染选中").action {
                        strOut.value = renderStr(ca.selectedText, kv)
                    }
                    item("清除").action {
                        ca.replaceText("")
                    }
                }
                paragraphGraphicFactory = LineNumberFactory.get(ca)
                isAutoScrollOnDragDesired = true
                prefWidth = 500.0
                multiPlainChanges().successionEnds(Duration.ofMillis(500))
                        .subscribe { ca.setStyleSpans(0, computeHighlighting(ca.text)) }
                replaceText(0, 0, sampleCode)

            }
            textarea(strOut) {
                prefHeight = 600.0
                isWrapText = true
                isEditable = false
                promptText="这是结果文本框"
            }
        }
    }

    private fun renderStr(inStr: String, kv: Kv): String {
        var result = ""
        try {
            result = engine.getTemplateByString(inStr).renderToString(kv)
        } catch (e: Exception) {
            information("模板中存在错误:$e")
        }

        return result
    }

    init {
        Pattern.compile(
                "(?<KEYWORD>" + KEYWORD_PATTERN + ")"
                        + "|(?<PAREN>" + PAREN_PATTERN + ")"
                        + "|(?<BRACE>" + BRACE_PATTERN + ")"
                        + "|(?<BRACKET>" + BRACKET_PATTERN + ")"
                        + "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
                        + "|(?<STRING>" + STRING_PATTERN + ")"
                        + "|(?<COMMENT>" + COMMENT_PATTERN + ")"
        )
    }
}

private val KEYWORDS = arrayOf("abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class",
        "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for",
        "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package",
        "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this",
        "throw", "throws", "transient", "try", "void", "volatile", "while", "end", "set", "number", "date")
private val KEYWORD_PATTERN = "\\b(" + KEYWORDS.joinToString("|") + ")\\b"
private val PAREN_PATTERN = "\\(|\\)"
private val BRACE_PATTERN = "\\{|\\}"
private val BRACKET_PATTERN = "\\[|\\]"
private val SEMICOLON_PATTERN = "\\;"
private val STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\""
private val COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/"

private val sampleCode = """
#number(3.1415926, "#.##")

#number(0.9518, "#.##%")

#number(300000, "光速为每秒，### 公里。")

### 算术运算
#(1 + 2 / 3 * 4)

### 比较运算
#(1 > 2)

### 逻辑运算
#set(a = 1, b = 2, c = a + b)
#(d=5)
#(e=5)
#(!a && b != c || d == e)

### 三元表达式
#(a > 0 ? a : b)

### 方法调用
#("abcdef".substring(0, 3))

#调用com.jfinal.kit.StrKit中的方法
#(firstCharToUpperCase("abcdef"))


#set(arr=[1,2,3])
#(arr[1])
#set(menu='index')
#(menu=='index' ? 'current' : 'normal')
#(menu!="index" ? "current" : "normal")
#if(menu=='index')
    #(menu)
#end
#if(menu!='index')
    #(normal)
#else
    #(menu)
#end

#set(map={k1:123,"k2":"abc"})
#(map.k1)
#(map.k2)
#(map["k1"])
#(map["k2"])
#(map.get("k1"))
#({1:'自买', 2:'跟买'}.get(1))
#({1:'自买', 2:'跟买'}[2])

### 与双问号符联合使用支持默认值
#({1:'自买', 2:'跟买'}.get(999) ?? '其它')

// 定义数组 array，并为元素赋默认值
#set(array = [123, "abc", true])
#set(array2 = [2123, "2abc", false])

### 获取下标为 1 的值，输出为: "abc"
#(array[1])

// 将下标为 1 的元素赋值为 false，并输出
#(array[1] = false, array[1])
###set(array = [ 123, "abc", true, a && b || c, 1 + 2, obj.doIt(x) ])

#for(x : [1..10])
   #(x)
#end

### 对 Map 进行迭代
#for(x : map)
  #(x.key)
  #(x.value)
#end

### 对 List、数组、Set 这类结构进行迭代
#for(x : array)
   #(for.size)
   #(for.index)
   #(for.count)
   #(for.first)
   #(for.last)
   #(for.odd)
   #(for.even)

  #for(x : array2)
     #(for.outer)
     #(for.outer.index)
     #(for.index)
  #end
#end

### 判断每月天数
#for(month:[1..12])
    #switch (month)
      #case (1, 3, 5, 7, 8, 10, 12)
        #(month) 月有 31 天
      #case (2)
        #(month) 月平年有28天，闰年有29天
      #default
       #(month) 月平年有30天
    #end
#end

#set(x = 123)

#set(array[0] = 123)
#set(map["key"] = 456)
 
#(x)  #(c)  #(array[0])  #(map.key)  #(map["key"])

#(x = 123, y = "abc", array = [1, "a", true], map = {k1:v1})

### 6.5 注释
#--
   这里是多行注释的第一行
   这里是多行注释的第二行
--#

6.6 原样输出
#[[
   #(value)
   #for(x : list)
      #(x.name)
   #end
]]#

10、#date 指令
今天的日期是：#(dt)
#date(dt, "yyyy-MM-dd HH:mm:ss")
#(dt.time)
#(dt.year)
#(dt.month)
#(dt.day)
#(dt.hours)
#(dt.date)
#(dt.minutes)
#(dt.seconds)
#(dt.timezoneOffset)
#(dt.toGMTString())
#(dt.toLocaleString())
""".trimIndent()

private fun computeHighlighting(text: String): StyleSpans<Collection<String>> {
    val PATTERN = Pattern.compile(
            "(?<KEYWORD>" + KEYWORD_PATTERN + ")"
                    + "|(?<PAREN>" + PAREN_PATTERN + ")"
                    + "|(?<BRACE>" + BRACE_PATTERN + ")"
                    + "|(?<BRACKET>" + BRACKET_PATTERN + ")"
                    + "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
                    + "|(?<STRING>" + STRING_PATTERN + ")"
                    + "|(?<COMMENT>" + COMMENT_PATTERN + ")"
    )
    val matcher = PATTERN.matcher(text)
    var lastKwEnd = 0
    val spansBuilder = StyleSpansBuilder<Collection<String>>()
    while (matcher.find()) {
        val styleClass = (if (matcher.group("KEYWORD") != null)
            "keyword"
        else if (matcher.group("PAREN") != null)
            "paren"
        else if (matcher.group("BRACE") != null)
            "brace"
        else if (matcher.group("BRACKET") != null)
            "bracket"
        else if (matcher.group("SEMICOLON") != null)
            "semicolon"
        else if (matcher.group("STRING") != null)
            "string"
        else if (matcher.group("COMMENT") != null)
            "comment"
        else
            null)!! /* never happens */
        spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd)
        spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start())
        lastKwEnd = matcher.end()
    }
    spansBuilder.add(Collections.emptyList(), text.length - lastKwEnd)
    return spansBuilder.create()
}

class Mystyle : Stylesheet() {
    companion object {
        val keyword by cssclass()
        val semicolon by cssclass()
        val paren by cssclass()
        val bracket by cssclass()
        val brace by cssclass()
        val string by cssclass()
        val comment by cssclass()
        val paragraphBox by cssclass()
    }

    init {
        s(text, textArea) {
            fontSize = 16.px
        }
        s(keyword, semicolon, paren, bracket, label) {
            fontWeight = FontWeight.BOLD
        }
        keyword {
            fill = Color.PURPLE
        }
        paren {
            fill = Color.FIREBRICK
        }
        bracket {
            fill = Color.DARKGREEN
        }
        brace {
            fill = Color.TEAL
        }
        string {
            fill = Color.BLUE
        }
        comment {
            fill = Color.CADETBLUE
        }
        paragraphBox {
            backgroundColor += c("#f2f9fc")
        }
    }
}

